/*
 * upload.c
 * 上传文件功能
 * Created on: 2018年5月2日
 * Author: cfm
 */

#define MULTIPART_FORM_DATA_STRING "multipart/form-data"
#define BOUNDARY_STRING "boundary="
#define CONTENT_DISPOSITION_STRING "Content-Disposition:"
#define CONTENT_TYPE_STRING "Content-Type:"
#define CONTENT_RANGE_STRING "Content-Range:"
#define X_CONTENT_RANGE_STRING "X-Content-Range:"
#define SESSION_ID_STRING "Session-ID:"
#define X_SESSION_ID_STRING "X-Session-ID:"
#define FORM_DATA_STRING "form-data"
#define ATTACHMENT_STRING "attachment"
#define FILENAME_STRING "filename=\""
#define FIELDNAME_STRING "name=\""
#define BYTES_UNIT_STRING "bytes "

/*
 * State of multipart/form-data parser
 */
typedef enum
{
    upload_state_boundary_seek,
    upload_state_after_boundary,
    upload_state_headers,
    upload_state_data,
    upload_state_finish
} upload_state_t;

/*
 * Range
 */
typedef struct
{
    off_t start, end, total;
} ngx_http_upload_range_t;

/*
 * State of range merger
 */
typedef struct
{
    ngx_buf_t *in_buf;
    ngx_buf_t *out_buf;
    ngx_http_upload_range_t current_range_n;
    off_t *parser_state;
    ngx_log_t *log;

    u_char *range_header_buffer;
    u_char *range_header_buffer_end;
    u_char **range_header_buffer_pos;

    unsigned int found_lower_bound : 1;
    unsigned int complete_ranges : 1;
    unsigned int first_range : 1;
} ngx_http_upload_merger_state_t;

/*
 * Upload cleanup record
 */
typedef struct ngx_http_upload_cleanup_s
{
    ngx_fd_t fd;
    u_char *filename;
    ngx_http_headers_out_t *headers_out;
    ngx_log_t *log;
    unsigned int aborted : 1;
    unsigned int clean : 1;
} ngx_upload_cleanup_t;

struct jjyun_upload_ctx_s;

/*
 * Request body data handler
 */
typedef ngx_int_t (*ngx_http_request_body_data_handler_pt)(struct jjyun_upload_ctx_s *, u_char *, u_char *);

/*
 * Upload module context
 */
typedef struct jjyun_upload_ctx_s
{
    ngx_str_t session_id;
    ngx_str_t boundary;
    u_char *boundary_start;
    u_char *boundary_pos;

    upload_state_t state;

    u_char *header_accumulator;
    u_char *header_accumulator_end;
    u_char *header_accumulator_pos;

    ngx_str_t field_name;
    ngx_str_t file_name;
    ngx_str_t content_type;
    ngx_str_t content_range;
    ngx_http_upload_range_t content_range_n;

    ngx_uint_t ordinal;

    u_char *output_buffer;
    u_char *output_buffer_end;
    u_char *output_buffer_pos;
    u_char *merge_buffer;
    u_char *range_header_buffer;
    u_char *range_header_buffer_pos;
    u_char *range_header_buffer_end;

    ngx_http_request_body_data_handler_pt data_handler;

    ngx_int_t (*start_part_f)(struct jjyun_upload_ctx_s *upload_ctx);
    void (*finish_part_f)(struct jjyun_upload_ctx_s *upload_ctx);
    void (*abort_part_f)(struct jjyun_upload_ctx_s *upload_ctx);
    ngx_int_t (*flush_output_buffer_f)(struct jjyun_upload_ctx_s *upload_ctx, u_char *buf, size_t len);

    ngx_http_request_t *request;
    ngx_log_t *log;

    ngx_file_t output_file;
    ngx_file_t state_file;
    ngx_chain_t *chain;
    ngx_chain_t *last;
    ngx_chain_t *checkpoint;
    ngx_chain_t *to_write;
    size_t output_body_len;
    size_t limit_rate;
    ssize_t received;

    ngx_pool_cleanup_t *cln;

    unsigned int first_part : 1;
    unsigned int discard_data : 1;
    unsigned int is_file : 1;
    unsigned int partial_content : 1;
    unsigned int prevent_output : 1;
    unsigned int calculate_crc32 : 1;
    unsigned int started : 1;
    unsigned int unencoded : 1;
    unsigned int no_content : 1;
    unsigned int raw_input : 1;
} jjyun_upload_ctx_t;

static void upload_discard_part_attributes(jjyun_upload_ctx_t *upload_ctx);

static ngx_int_t ngx_http_upload_start_handler(jjyun_upload_ctx_t *u);
static void ngx_http_upload_finish_handler(jjyun_upload_ctx_t *u);
static void ngx_http_upload_abort_handler(jjyun_upload_ctx_t *u);
static ngx_int_t ngx_http_upload_flush_output_buffer(jjyun_upload_ctx_t *u, u_char *buf, size_t len);

static ngx_int_t upload_start_file(jjyun_upload_ctx_t *upload_ctx);
static void upload_finish_file(jjyun_upload_ctx_t *upload_ctx);
static void upload_abort_file(jjyun_upload_ctx_t *upload_ctx);
static void upload_flush_output_buffer(jjyun_upload_ctx_t *upload_ctx);

static ngx_int_t ngx_http_upload_merge_ranges(jjyun_upload_ctx_t *u, ngx_http_upload_range_t *range_n);
static ngx_int_t upload_process_buf(jjyun_upload_ctx_t *upload_ctx, u_char *start, u_char *end);
static ngx_int_t upload_process_raw_buf(jjyun_upload_ctx_t *upload_ctx, u_char *start, u_char *end);

//初始化与释放
#define get_jjyun_upload_ctx_t() \
    (jjyun_upload_ctx_t *)(((ngx_http_jjyun_ctx_t *)ngx_http_get_module_ctx(r, ngx_http_jjyun_module))->type_ctx)

static void upload_init_ctx(jjyun_upload_ctx_t *upload_ctx, ngx_http_request_t *r)
{
    upload_ctx->log = r->connection->log;
    upload_ctx->request = r;

    upload_ctx->boundary.data = 0;
    upload_ctx->boundary_start = 0;
    upload_ctx->boundary_pos = 0;

    upload_ctx->state = upload_state_boundary_seek;

    upload_discard_part_attributes(upload_ctx);

    upload_ctx->discard_data = 0;

    upload_ctx->start_part_f = ngx_http_upload_start_handler;
    upload_ctx->finish_part_f = ngx_http_upload_finish_handler;
    upload_ctx->abort_part_f = ngx_http_upload_abort_handler;
    upload_ctx->flush_output_buffer_f = ngx_http_upload_flush_output_buffer;

    upload_ctx->started = 0;
    upload_ctx->unencoded = 0;
    upload_ctx->data_handler = upload_process_buf;
}
static void upload_shutdown_ctx(jjyun_upload_ctx_t *upload_ctx)
{
    if (upload_ctx != 0)
    {
        // Abort file if we still processing it
        if (upload_ctx->state == upload_state_data)
        {
            upload_flush_output_buffer(upload_ctx);
            upload_abort_file(upload_ctx);
        }
        upload_discard_part_attributes(upload_ctx);
    }
}

static ngx_int_t upload_start(jjyun_upload_ctx_t *upload_ctx)
{
    if (upload_ctx == NULL)
        return NGX_ERROR;

    upload_ctx->header_accumulator = ngx_pcalloc(upload_ctx->request->pool, 512 + 1);
    if (upload_ctx->header_accumulator == NULL)
        return NGX_ERROR;
    upload_ctx->header_accumulator_pos = upload_ctx->header_accumulator;
    upload_ctx->header_accumulator_end = upload_ctx->header_accumulator + 512;

    upload_ctx->output_buffer = ngx_pcalloc(upload_ctx->request->pool, ngx_pagesize);
    if (upload_ctx->output_buffer == NULL)
        return NGX_ERROR;
    upload_ctx->output_buffer_pos = upload_ctx->output_buffer;
    upload_ctx->output_buffer_end = upload_ctx->output_buffer + ngx_pagesize;

    upload_ctx->range_header_buffer = ngx_pcalloc(upload_ctx->request->pool, 256);
    if (upload_ctx->range_header_buffer == NULL)
        return NGX_ERROR;
    upload_ctx->range_header_buffer_pos = upload_ctx->range_header_buffer;
    upload_ctx->range_header_buffer_end = upload_ctx->range_header_buffer + 256;

    upload_ctx->first_part = 1;
    return NGX_OK;
}

/**
    头部解析相关
*/
//校验sessionid标准性
static ngx_int_t upload_validate_session_id(ngx_str_t *session_id)
{
    u_char *p, *q;

    p = session_id->data;
    q = session_id->data + session_id->len;

    while (p != q)
    {
        if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_' || *p == '-'))
        {
            return NGX_ERROR;
        }

        p++;
    }

    return NGX_OK;
}

static ngx_int_t upload_parse_content_disposition(jjyun_upload_ctx_t *upload_ctx, ngx_str_t *content_disposition)
{
    char *filename_start, *filename_end;
    char *fieldname_start, *fieldname_end;
    char *p, *q;

    p = (char *)content_disposition->data;

    if (strncasecmp(FORM_DATA_STRING, p, sizeof(FORM_DATA_STRING) - 1) &&
        strncasecmp(ATTACHMENT_STRING, p, sizeof(ATTACHMENT_STRING) - 1))
    {
        ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0,
                       "Content-Disposition is not form-data or attachment");
        return NGX_ERROR;
    }

    filename_start = strstr(p, FILENAME_STRING);

    if (filename_start != 0)
    {

        filename_start += sizeof(FILENAME_STRING) - 1;

        filename_end = filename_start + strcspn(filename_start, "\"");

        if (*filename_end != '\"')
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0,
                           "malformed filename in part header");
            return NGX_ERROR;
        }

        /*
         * IE sends full path, strip path from filename 
         * Also strip all UNIX path references
         */
        for (q = filename_end - 1; q > filename_start; q--)
            if (*q == '\\' || *q == '/')
            {
                filename_start = q + 1;
                break;
            }

        upload_ctx->file_name.len = filename_end - filename_start;
        upload_ctx->file_name.data = ngx_palloc(upload_ctx->request->pool, upload_ctx->file_name.len + 1);

        if (upload_ctx->file_name.data == NULL)
            return NGX_ERROR;

        strncpy((char *)upload_ctx->file_name.data, filename_start, filename_end - filename_start);
    }

    fieldname_start = p;

    fieldname_start = strstr(fieldname_start, FIELDNAME_STRING);

    if (fieldname_start != 0)
    {
        fieldname_start += sizeof(FIELDNAME_STRING) - 1;

        if (fieldname_start != filename_start)
        {
            fieldname_end = fieldname_start + strcspn(fieldname_start, "\"");

            if (*fieldname_end != '\"')
            {
                ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0,
                              "malformed fieldname in part header");
                return NGX_ERROR;
            }

            upload_ctx->field_name.len = fieldname_end - fieldname_start;
            upload_ctx->field_name.data = ngx_pcalloc(upload_ctx->request->pool, upload_ctx->field_name.len + 1);

            if (upload_ctx->field_name.data == NULL)
                return NGX_ERROR;

            strncpy((char *)upload_ctx->field_name.data, fieldname_start, fieldname_end - fieldname_start);
        }
    }

    return NGX_OK;
}

static ngx_int_t upload_parse_range(ngx_str_t *range, ngx_http_upload_range_t *range_n)
{
    u_char *p = range->data;
    u_char *last = range->data + range->len;
    off_t *field = &range_n->start;

    if (range_n == NULL)
        return NGX_ERROR;

    do
    {
        *field = 0;

        while (p < last)
        {

            if (*p >= '0' && *p <= '9')
            {
                (*field) = (*field) * 10 + (*p - '0');
            }
            else if (*p == '-')
            {
                if (field != &range_n->start)
                {
                    return NGX_ERROR;
                }

                field = &range_n->end;
                p++;
                break;
            }
            else if (*p == '/')
            {
                if (field != &range_n->end)
                {
                    return NGX_ERROR;
                }

                field = &range_n->total;
                p++;
                break;
            }
            else
            {
                return NGX_ERROR;
            }

            p++;
        }
    } while (p < last);

    if (field != &range_n->total)
    {
        return NGX_ERROR;
    }

    if (range_n->start > range_n->end || range_n->start >= range_n->total || range_n->end >= range_n->total)
    {
        return NGX_ERROR;
    }

    return NGX_OK;
}

static ngx_int_t upload_parse_request_headers(jjyun_upload_ctx_t *upload_ctx, ngx_http_headers_in_t *headers_in)
{
    ngx_str_t *content_type, s;
    ngx_list_part_t *part;
    ngx_table_elt_t *header;
    ngx_uint_t i;
    u_char *mime_type_end_ptr;
    u_char *boundary_start_ptr, *boundary_end_ptr;
    ngx_atomic_uint_t boundary;

    // Check whether Content-Type header is missing
    if (headers_in->content_type == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, upload_ctx->log, ngx_errno, "missing Content-Type header");
        return NGX_HTTP_BAD_REQUEST;
    }

    content_type = &headers_in->content_type->value;

    if (ngx_strncasecmp(content_type->data, (u_char *)MULTIPART_FORM_DATA_STRING, sizeof(MULTIPART_FORM_DATA_STRING) - 1))
    {
        /*
         * Content-Type is not multipart/form-data,
         * look for Content-Disposition header now
         */
        part = &headers_in->headers.part;
        header = part->elts;

        for (i = 0;; i++)
        {
            if (i >= part->nelts)
            {
                if (part->next == NULL)
                {
                    break;
                }

                part = part->next;
                header = part->elts;
                i = 0;
            }

            if (!strncasecmp(CONTENT_DISPOSITION_STRING, (char *)header[i].key.data, sizeof(CONTENT_DISPOSITION_STRING) - 1 - 1))
            {
                if (upload_parse_content_disposition(upload_ctx, &header[i].value))
                {
                    ngx_log_error(NGX_LOG_INFO, upload_ctx->log, 0, "invalid Content-Disposition header");
                    return NGX_ERROR;
                }
                upload_ctx->is_file = 1;
                upload_ctx->unencoded = 1;
                upload_ctx->raw_input = 1;

                upload_ctx->data_handler = upload_process_raw_buf;
            }
            else if (!strncasecmp(SESSION_ID_STRING, (char *)header[i].key.data, sizeof(SESSION_ID_STRING) - 1 - 1) || !strncasecmp(X_SESSION_ID_STRING, (char *)header[i].key.data, sizeof(X_SESSION_ID_STRING) - 1 - 1))
            {
                if (header[i].value.len == 0)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "empty Session-ID in header");
                    return NGX_ERROR;
                }

                if (upload_validate_session_id(&header[i].value) != NGX_OK)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "invalid Session-ID in header");
                    return NGX_ERROR;
                }

                upload_ctx->session_id = header[i].value;
            }
            else if (!strncasecmp(CONTENT_RANGE_STRING, (char *)header[i].key.data, sizeof(CONTENT_RANGE_STRING) - 1 - 1) || !strncasecmp(X_CONTENT_RANGE_STRING, (char *)header[i].key.data, sizeof(X_CONTENT_RANGE_STRING) - 1 - 1))
            {
                if (header[i].value.len == 0)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "empty Content-Range in part header");
                    return NGX_ERROR;
                }

                if (strncasecmp((char *)header[i].value.data, BYTES_UNIT_STRING, sizeof(BYTES_UNIT_STRING) - 1))
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "unsupported range unit");
                    return NGX_ERROR;
                }

                s.data = (u_char *)(char *)header[i].value.data + sizeof(BYTES_UNIT_STRING) - 1;
                s.len = header[i].value.len - sizeof(BYTES_UNIT_STRING) + 1;

                if (upload_parse_range(&s, &upload_ctx->content_range_n) != NGX_OK)
                {
                    ngx_log_debug2(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "invalid range %V (%V)", &s, &header[i].value);
                    return NGX_ERROR;
                }

                ngx_log_debug3(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "partial content, range %O-%O/%O", upload_ctx->content_range_n.start,
                               upload_ctx->content_range_n.end, upload_ctx->content_range_n.total);

                if ((upload_ctx->content_range_n.end - upload_ctx->content_range_n.start + 1) != headers_in->content_length_n)
                {
                    ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0, "range length is not equal to content length");
                    return NGX_HTTP_RANGE_NOT_SATISFIABLE;
                }

                upload_ctx->partial_content = 1;
            }
        }
        if (!upload_ctx->unencoded)
        {
            ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0,
                          "Content-Type is not multipart/form-data and no Content-Disposition header found");
            return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
        }

        upload_ctx->content_type = *content_type;

        boundary = ngx_next_temp_number(0);

        content_type->data =
            ngx_pnalloc(upload_ctx->request->pool,
                        sizeof(MULTIPART_FORM_DATA_STRING "; boundary=") - 1 + NGX_ATOMIC_T_LEN);

        if (content_type->data == NULL)
        {
            return NGX_ERROR;
        }

        content_type->len =
            ngx_sprintf(content_type->data,
                        MULTIPART_FORM_DATA_STRING "; boundary=%0muA",
                        boundary) -
            content_type->data;

        boundary_start_ptr = content_type->data + sizeof(MULTIPART_FORM_DATA_STRING "; boundary=") - 1;
        boundary_end_ptr = content_type->data + content_type->len;
    }
    else
    {
        mime_type_end_ptr = (u_char *)ngx_strchr(content_type->data, ';');

        upload_ctx->boundary.data = 0;

        if (mime_type_end_ptr == NULL)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "no boundary found in Content-Type");
            return NGX_ERROR;
        }

        boundary_start_ptr = ngx_strstrn(mime_type_end_ptr, BOUNDARY_STRING, sizeof(BOUNDARY_STRING) - 2);

        if (boundary_start_ptr == NULL)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "no boundary found in Content-Type");
            return NGX_ERROR;
        }

        boundary_start_ptr += sizeof(BOUNDARY_STRING) - 1;
        boundary_end_ptr = boundary_start_ptr + strcspn((char *)boundary_start_ptr, " ;\n\r");

        if (boundary_end_ptr == boundary_start_ptr)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0, "boundary is empty");
            return NGX_ERROR;
        }
    }
    upload_ctx->boundary.len = boundary_end_ptr - boundary_start_ptr + 4;
    upload_ctx->boundary.data = ngx_palloc(upload_ctx->request->pool, upload_ctx->boundary.len + 1);

    if (upload_ctx->boundary.data == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;

    ngx_cpystrn(upload_ctx->boundary.data + 4, boundary_start_ptr, boundary_end_ptr - boundary_start_ptr + 1);

    upload_ctx->boundary.data[0] = '\r';
    upload_ctx->boundary.data[1] = '\n';
    upload_ctx->boundary.data[2] = '-';
    upload_ctx->boundary.data[3] = '-';

    upload_ctx->boundary_start = upload_ctx->boundary.data + 2;
    upload_ctx->boundary_pos = upload_ctx->boundary_start;
    return NGX_OK;
}
static ngx_int_t upload_parse_part_header(jjyun_upload_ctx_t *upload_ctx, char *header, char *header_end)
{
    ngx_str_t s;

    if (!strncasecmp(CONTENT_DISPOSITION_STRING, header, sizeof(CONTENT_DISPOSITION_STRING) - 1))
    {
        char *p = header + sizeof(CONTENT_DISPOSITION_STRING) - 1;

        p += strspn(p, " ");

        s.data = (u_char *)p;
        s.len = header_end - p;

        if (upload_parse_content_disposition(upload_ctx, &s) != NGX_OK)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, upload_ctx->log, 0,
                           "invalid Content-Disposition header");
            return NGX_ERROR;
        }
    }
    else if (!strncasecmp(CONTENT_TYPE_STRING, header, sizeof(CONTENT_TYPE_STRING) - 1))
    {
        char *content_type_str = header + sizeof(CONTENT_TYPE_STRING) - 1;

        content_type_str += strspn(content_type_str, " ");
        upload_ctx->content_type.len = header_end - content_type_str;

        if (upload_ctx->content_type.len == 0)
        {
            ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0,
                          "empty Content-Type in part header");
            return NGX_ERROR; // Empty Content-Type field
        }

        upload_ctx->content_type.data = ngx_pcalloc(upload_ctx->request->pool, upload_ctx->content_type.len + 1);

        if (upload_ctx->content_type.data == NULL)
            return NGX_ERROR; // Unable to allocate memory for string

        strncpy((char *)upload_ctx->content_type.data, content_type_str, upload_ctx->content_type.len);
    }

    return NGX_OK;
}
/**
 * 文件内容处理相关
*/

static void upload_discard_part_attributes(jjyun_upload_ctx_t *upload_ctx)
{
    upload_ctx->file_name.len = 0;
    upload_ctx->file_name.data = NULL;

    upload_ctx->field_name.len = 0;
    upload_ctx->field_name.data = NULL;

    upload_ctx->content_type.len = 0;
    upload_ctx->content_type.data = NULL;

    upload_ctx->content_range.len = 0;
    upload_ctx->content_range.data = NULL;

    upload_ctx->session_id.len = 0;
    upload_ctx->session_id.data = NULL;

    upload_ctx->partial_content = 0;
}

static void ngx_upload_cleanup_handler(void *data)
{
    ngx_upload_cleanup_t *cln = data;
    if (!cln->aborted)
    {
        if (cln->fd >= 0)
        {
            if (ngx_close_file(cln->fd) == NGX_FILE_ERROR)
            {
                ngx_log_error(NGX_LOG_ALERT, cln->log, ngx_errno,
                              ngx_close_file_n " \"%s\" failed", cln->filename);
            }
        }
    }
    if (cln->clean)
    {
        if (ngx_delete_file(cln->filename) == NGX_FILE_ERROR)
        {
            ngx_log_error(NGX_LOG_ALERT, cln->log, ngx_errno,
                          ngx_delete_file_n " \"%s\" failed", cln->filename);
        }
    }
}

static ngx_int_t ngx_http_upload_start_handler(jjyun_upload_ctx_t *u)
{
    ngx_http_request_t *r = u->request;
    ngx_file_t *file = &u->output_file;
    ngx_http_jjyun_conf_t *jjcf = ngx_http_get_module_loc_conf(r, ngx_http_jjyun_module);
    ngx_path_t *path = jjcf->store_path;
    ngx_path_t *state_path = jjcf->state_store_path;
    uint32_t n;
    ngx_err_t err;
    ngx_upload_cleanup_t *ucln;
    ngx_str_t ext;

    if (u->is_file)
    {
        u->ordinal++;
        u->cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_upload_cleanup_t));
        if (u->cln == NULL)
            return NGX_ERROR;

        ext.data = (u_char *)ngx_strchr(u->file_name.data, '.');
        ext.len = u->file_name.data + u->file_name.len - ext.data;

        file->name.len = path->name.len + 1 + path->len + (u->session_id.len != 0 ? u->session_id.len : 10) + ext.len;

        file->name.data = ngx_palloc(u->request->pool, file->name.len + 1);
        if (file->name.data == NULL)
            return NGX_ERROR;

        ngx_memcpy(file->name.data, path->name.data, path->name.len);

        file->log = r->connection->log;

        if (u->session_id.len != 0)
        {
            (void)ngx_sprintf(file->name.data + path->name.len + 1 + path->len,
                              "%V%V%Z", &u->session_id, &ext);

            ngx_create_hashed_filename(path, file->name.data, file->name.len);

            ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
                           "hashed path: %s", file->name.data);

            if (u->partial_content)
            {
                ngx_file_t *state_file = &u->state_file;
                if (u->merge_buffer == NULL)
                {
                    u->merge_buffer = ngx_palloc(r->pool, ngx_pagesize >> 1);

                    if (u->merge_buffer == NULL)
                        return NGX_ERROR;
                }

                state_file->name.len = state_path->name.len + 1 + state_path->len + u->session_id.len + sizeof(".state") - 1;
                state_file->name.data = ngx_palloc(u->request->pool, state_file->name.len + 1);

                if (state_file->name.data == NULL)
                    return NGX_ERROR;

                ngx_memcpy(state_file->name.data, state_path->name.data, state_path->name.len);
                (void)ngx_sprintf(state_file->name.data + state_path->name.len + 1 + state_path->len,
                                  "%V.state%Z", &u->session_id);

                ngx_create_hashed_filename(state_path, state_file->name.data, state_file->name.len);

                ngx_create_full_path(state_file->name.data, 0700);

                ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
                               "hashed path of state file: %s", state_file->name.data);
            }

            file->fd = ngx_open_file(file->name.data, NGX_FILE_WRONLY, NGX_FILE_CREATE_OR_OPEN, 0600);

            if (file->fd == NGX_INVALID_FILE)
            {
                err = ngx_errno;

                ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
                              "failed to create output file \"%V\" for \"%V\"", &file->name, &u->file_name);
                return NGX_ERROR;
            }

            file->offset = u->content_range_n.start;
        }
        else
        {
            for (;;)
            {
                n = (uint32_t)ngx_next_temp_number(0);

                (void)ngx_sprintf(file->name.data + path->name.len + 1 + path->len,
                                  "%010uD%V%Z", n, &ext);

                ngx_create_hashed_filename(path, file->name.data, file->name.len);

                ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
                               "hashed path: %s", file->name.data);

                file->fd = ngx_open_tempfile(file->name.data, 1, 0600);

                if (file->fd != NGX_INVALID_FILE)
                {
                    file->offset = 0;
                    break;
                }

                err = ngx_errno;

                if (err == NGX_EEXIST)
                {
                    n = (uint32_t)ngx_next_temp_number(1);
                    continue;
                }

                ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
                              "failed to create output file \"%V\" for \"%V\"", &file->name, &u->file_name);
                return NGX_ERROR;
            }
        }

        u->cln->handler = ngx_upload_cleanup_handler;

        ucln = u->cln->data;
        ucln->fd = file->fd;
        ucln->filename = file->name.data;
        ucln->log = r->connection->log;
        ucln->headers_out = &r->headers_out;
        ucln->aborted = 0;
        ucln->clean = 0;

        if (u->partial_content)
        {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "started uploading part %O-%O/%O of file \"%V\" to \"%V\" (field \"%V\", content type \"%V\")",
                          u->content_range_n.start, u->content_range_n.end, u->content_range_n.total, &u->file_name,
                          &u->output_file.name, &u->field_name, &u->content_type);
        }
        else
        {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "started uploading file \"%V\" to \"%V\" (field \"%V\", content type \"%V\")",
                          &u->file_name, &u->output_file.name, &u->field_name, &u->content_type);
        }
    }
    else
    {
        u->discard_data = 1;
    }

    return NGX_OK;
}

static void ngx_http_upload_finish_handler(jjyun_upload_ctx_t *u)
{
    ngx_http_request_t *r = u->request;
    ngx_upload_cleanup_t *ucln;
    ngx_int_t rc;
    if (u->is_file)
    {
        ucln = u->cln->data;
        ucln->fd = -1;

        ngx_close_file(u->output_file.fd);

        if (u->partial_content)
        {
            if (u->output_file.offset != u->content_range_n.end + 1)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "file offset at the end of a part %O does not match the end specified range %O-%O/%O", u->output_file.offset, u->content_range_n.start, u->content_range_n.end, u->content_range_n.total, u->output_file.name);
                goto rollback;
            }
            rc = ngx_http_upload_merge_ranges(u, &u->content_range_n);
            if (rc == NGX_ERROR)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "error merging ranges");
                goto rollback;
            }
            if (rc == NGX_AGAIN)
            {
                /*
                     * If there are more parts to go, we do not produce any output
                     */
                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "finished uploading part %O-%O/%O of a file \"%V\" to \"%V\"", u->content_range_n.start, u->content_range_n.end, u->content_range_n.total, &u->file_name, &u->output_file.name);
                u->prevent_output = 1;
                return;
            }

            if (ngx_delete_file(u->state_file.name.data) == NGX_FILE_ERROR)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to remove state file \"%V\"", &u->state_file.name);
            }
            else
            {
                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "removed state file \"%V\"", &u->state_file.name);
            }
        }
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "finished uploading file \"%V\" to \"%V\"", &u->file_name, &u->output_file.name);
    }

    // Checkpoint current output chain state
    u->checkpoint = u->last;
    return;

rollback:
    ngx_http_upload_abort_handler(u);
}

static void ngx_http_upload_abort_handler(jjyun_upload_ctx_t *u)
{
    ngx_upload_cleanup_t *ucln;

    if (u->is_file)
    {
        ucln = u->cln->data;
        ucln->fd = -1;
        ucln->aborted = 1;

        ngx_close_file(u->output_file.fd);

        if (!u->partial_content)
        {
            if (ngx_delete_file(u->output_file.name.data) == NGX_FILE_ERROR)
            {
                ngx_log_error(NGX_LOG_ERR, u->log, ngx_errno, "aborted uploading file \"%V\" to \"%V\", failed to remove destination file", &u->file_name, &u->output_file.name);
            }
            else
            {
                ngx_log_error(NGX_LOG_ALERT, u->log, 0, "aborted uploading file \"%V\" to \"%V\", dest file removed", &u->file_name, &u->output_file.name);
            }
        }
    }

    // Rollback output chain to the previous consistant state
    if (u->checkpoint != NULL)
    {
        u->last = u->checkpoint;
        u->last->next = NULL;
    }
    else
    {
        u->chain = u->last = NULL;
        u->first_part = 1;
    }
}

static ngx_int_t ngx_http_upload_flush_output_buffer(jjyun_upload_ctx_t *u, u_char *buf, size_t len)
{
    ngx_http_request_t *r = u->request;
    if (u->partial_content)
    {
        if (u->output_file.offset > u->content_range_n.end)
            return NGX_OK;

        if (u->output_file.offset + (off_t)len > u->content_range_n.end + 1)
            len = u->content_range_n.end - u->output_file.offset + 1;
    }

    if (ngx_write_file(&u->output_file, buf, len, u->output_file.offset) == NGX_ERROR)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
                      "write to file \"%V\" failed", &u->output_file.name);
        return NGX_ERROR;
    }
    else
        return NGX_OK;
}

static ngx_int_t upload_start_file(jjyun_upload_ctx_t *upload_ctx)
{
    if (upload_ctx->start_part_f)
        return upload_ctx->start_part_f(upload_ctx);
    else
        return NGX_OK;
}

static void upload_finish_file(jjyun_upload_ctx_t *upload_ctx)
{
    // Call user-defined event handler
    if (upload_ctx->finish_part_f)
        upload_ctx->finish_part_f(upload_ctx);

    upload_discard_part_attributes(upload_ctx);

    upload_ctx->discard_data = 0;
}

static void upload_abort_file(jjyun_upload_ctx_t *upload_ctx)
{
    if (upload_ctx->abort_part_f)
        upload_ctx->abort_part_f(upload_ctx);

    upload_discard_part_attributes(upload_ctx);

    upload_ctx->discard_data = 0;
}

static void upload_flush_output_buffer(jjyun_upload_ctx_t *upload_ctx)
{
    if (upload_ctx->output_buffer_pos > upload_ctx->output_buffer)
    {
        if (upload_ctx->flush_output_buffer_f)
            if (upload_ctx->flush_output_buffer_f(upload_ctx, (void *)upload_ctx->output_buffer,
                                                  (size_t)(upload_ctx->output_buffer_pos - upload_ctx->output_buffer)) != NGX_OK)
                upload_ctx->discard_data = 1;

        upload_ctx->output_buffer_pos = upload_ctx->output_buffer;
    }
}

static void upload_putc(jjyun_upload_ctx_t *upload_ctx, u_char c)
{
    if (!upload_ctx->discard_data)
    {
        *upload_ctx->output_buffer_pos = c;

        upload_ctx->output_buffer_pos++;

        if (upload_ctx->output_buffer_pos == upload_ctx->output_buffer_end)
            upload_flush_output_buffer(upload_ctx);
    }
}

static ngx_int_t ngx_http_upload_add_range(ngx_http_upload_merger_state_t *ms, ngx_http_upload_range_t *range_n)
{
    ms->out_buf->last = ngx_sprintf(ms->out_buf->last, "%O-%O/%O\x0a",
                                    range_n->start,
                                    range_n->end,
                                    range_n->total);

    if (*ms->range_header_buffer_pos < ms->range_header_buffer_end)
    {
        *ms->range_header_buffer_pos = ngx_sprintf(*ms->range_header_buffer_pos,
                                                   ms->first_range ? "%O-%O/%O" : ",%O-%O/%O",
                                                   range_n->start,
                                                   range_n->end,
                                                   range_n->total);

        ms->first_range = 0;
    }

    return NGX_OK;
}

static ngx_int_t ngx_http_upload_buf_merge_range(ngx_http_upload_merger_state_t *ms, ngx_http_upload_range_t *range_n)
{
    u_char *p, c;
    off_t *field;

    p = ms->in_buf->pos;

    field = ms->parser_state;

    do
    {
        *field = 0;

        while (p != ms->in_buf->last)
        {

            c = *p++;

            if (c >= '0' && c <= '9')
            {
                (*field) = (*field) * 10 + (c - '0');
            }
            else if (c == '-')
            {
                if (field != &ms->current_range_n.start)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "unexpected - while parsing range");
                    return NGX_ERROR;
                }

                field = &ms->current_range_n.end;
                break;
            }
            else if (c == '/')
            {
                if (field != &ms->current_range_n.end)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "unexpected / while parsing range");
                    return NGX_ERROR;
                }

                field = &ms->current_range_n.total;
                break;
            }
            else if (c == LF)
            {
                if (field != &ms->current_range_n.total)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "unexpected end of line while parsing range");
                    return NGX_ERROR;
                }

                if (ms->current_range_n.start >= ms->current_range_n.end || ms->current_range_n.start >= ms->current_range_n.total || ms->current_range_n.end > ms->current_range_n.total)
                {
                    ngx_log_debug3(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "inconsistent bounds while parsing range: %O-%O/%O",
                                   ms->current_range_n.start,
                                   ms->current_range_n.end,
                                   ms->current_range_n.total);
                    return NGX_ERROR;
                }

                if (ms->current_range_n.total != range_n->total)
                {
                    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "total number of bytes mismatch while parsing range");
                    return NGX_ERROR;
                }

                field = &ms->current_range_n.start;

                if (ms->current_range_n.end + 1 < range_n->start)
                {
                    /*
                     * Current range is entirely below the new one,
                     * output current one and seek next
                     */
                    if (ngx_http_upload_add_range(ms, &ms->current_range_n) != NGX_OK)
                    {
                        return NGX_ERROR;
                    }

                    ngx_log_debug3(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "< %O-%O/%O", ms->current_range_n.start,
                                   ms->current_range_n.end, ms->current_range_n.total);
                    break;
                }

                if (ms->current_range_n.start > range_n->end + 1)
                {
                    /*
                     * Current range is entirely above the new one,
                     * insert new range
                     */
                    if (!ms->found_lower_bound)
                    {
                        if (ngx_http_upload_add_range(ms, range_n) != NGX_OK)
                        {
                            return NGX_ERROR;
                        }
                    }

                    if (ngx_http_upload_add_range(ms, &ms->current_range_n) != NGX_OK)
                    {
                        return NGX_ERROR;
                    }

                    ngx_log_debug6(NGX_LOG_DEBUG_CORE, ms->log, 0,
                                   "> %O-%O/%O %O-%O/%O",
                                   range_n->start,
                                   range_n->end,
                                   range_n->total,
                                   ms->current_range_n.start,
                                   ms->current_range_n.end,
                                   ms->current_range_n.total);

                    ms->found_lower_bound = 1;
                    break;
                }

                /*
                 * Extend range to be merged with the current range
                 */
                range_n->start = range_n->start < ms->current_range_n.start ? range_n->start : ms->current_range_n.start;
                range_n->end = range_n->end > ms->current_range_n.end ? range_n->end : ms->current_range_n.end;
                break;
            }
            else
            {
                ngx_log_debug1(NGX_LOG_DEBUG_CORE, ms->log, 0,
                               "unexpected character %c", *p);
                return NGX_ERROR;
            }
        }
    } while (p != ms->in_buf->last);

    if (ms->in_buf->last_buf)
    {
        if (field != &ms->current_range_n.start)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_CORE, ms->log, 0,
                           "unexpected end of file while merging ranges");
            return NGX_ERROR;
        }

        if (!ms->found_lower_bound)
        {
            if (ngx_http_upload_add_range(ms, range_n) != NGX_OK)
            {
                return NGX_ERROR;
            }

            ngx_log_debug3(NGX_LOG_DEBUG_CORE, ms->log, 0,
                           "a %O-%O/%O",
                           range_n->start,
                           range_n->end,
                           range_n->total);

            ms->complete_ranges = (range_n->start == 0) && (range_n->end == range_n->total - 1) ? 1 : 0;

            ms->found_lower_bound = 1;
        }
    }

    ms->parser_state = field;

    return NGX_OK;
}

static ngx_int_t ngx_http_upload_merge_ranges(jjyun_upload_ctx_t *u, ngx_http_upload_range_t *range_n)
{
    ngx_file_t *state_file = &u->state_file;
    ngx_http_upload_merger_state_t ms;
    off_t remaining;
    ssize_t rc;
    __attribute__((__unused__)) int result;
    ngx_buf_t in_buf;
    ngx_buf_t out_buf;
    ngx_http_upload_range_t range_to_merge_n;

    state_file->fd = ngx_open_file(state_file->name.data, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN, 0600);

    if (state_file->fd == NGX_INVALID_FILE)
    {
        ngx_log_error(NGX_LOG_ERR, u->log, ngx_errno,
                      "failed to create or open state file \"%V\"", &state_file->name);
        return NGX_ERROR;
    }

    ngx_lock_fd(state_file->fd);

    ngx_fd_info(state_file->fd, &state_file->info);

    state_file->offset = 0;
    state_file->log = u->log;

    ms.in_buf = &in_buf;
    ms.out_buf = &out_buf;
    ms.parser_state = &ms.current_range_n.start;
    ms.log = u->log;

    ms.found_lower_bound = 0;
    ms.complete_ranges = 0;
    ms.first_range = 1;

    ms.range_header_buffer = u->range_header_buffer;
    ms.range_header_buffer_pos = &u->range_header_buffer_pos;
    ms.range_header_buffer_end = u->range_header_buffer_end;

    range_to_merge_n = *range_n;

    out_buf.start = out_buf.pos = out_buf.last = u->merge_buffer;
    out_buf.end = u->merge_buffer + ((size_t)ngx_pagesize >> 1 >> 1) + NGX_OFF_T_LEN * 3 + 2 + 1;
    out_buf.file_pos = 0;

    in_buf.start = in_buf.pos = in_buf.last = out_buf.end;
    in_buf.end = u->merge_buffer + ((size_t)ngx_pagesize >> 1);

    do
    {
        in_buf.file_pos = state_file->offset;
        in_buf.pos = in_buf.last = in_buf.start;

        if (state_file->offset < state_file->info.st_size)
        {
            remaining = state_file->info.st_size - state_file->offset > in_buf.end - in_buf.start
                            ? in_buf.end - in_buf.start
                            : state_file->info.st_size - state_file->offset;

            rc = ngx_read_file(state_file, in_buf.pos, remaining, state_file->offset);

            if (rc < 0 || rc != remaining)
            {
                goto failed;
            }

            in_buf.last = in_buf.pos + rc;
        }

        in_buf.last_buf = state_file->offset == state_file->info.st_size ? 1 : 0;

        if (out_buf.pos != out_buf.last)
        {
            rc = ngx_write_file(state_file, out_buf.pos, out_buf.last - out_buf.pos, out_buf.file_pos);

            if (rc < 0 || rc != out_buf.last - out_buf.pos)
            {
                goto failed;
            }

            out_buf.file_pos += out_buf.last - out_buf.pos;
        }

        out_buf.pos = out_buf.last = out_buf.start;

        if (ngx_http_upload_buf_merge_range(&ms, &range_to_merge_n) != NGX_OK)
        {
            ngx_log_error(NGX_LOG_ERR, u->log, 0,
                          "state file \"%V\" is corrupt", &state_file->name);
            rc = NGX_ERROR;
            goto failed;
        }
    } while (state_file->offset < state_file->info.st_size);

    if (out_buf.pos != out_buf.last)
    {
        rc = ngx_write_file(state_file, out_buf.pos, out_buf.last - out_buf.pos, out_buf.file_pos);

        if (rc < 0 || rc != out_buf.last - out_buf.pos)
        {
            goto failed;
        }

        out_buf.file_pos += out_buf.last - out_buf.pos;
    }

    if (out_buf.file_pos < state_file->info.st_size)
    {
        result = ftruncate(state_file->fd, out_buf.file_pos);
    }

    rc = ms.complete_ranges ? NGX_OK : NGX_AGAIN;

failed:
    ngx_unlock_fd(state_file->fd);

    ngx_close_file(state_file->fd);

    return rc;
}

static ngx_int_t upload_process_buf(jjyun_upload_ctx_t *upload_ctx, u_char *start, u_char *end)
{

    u_char *p;
    ngx_int_t rc;

    // No more data?
    if (start == end)
    {
        if (upload_ctx->state != upload_state_finish)
        {
            ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0, "premature end of body");
            return NGX_ERROR; // Signal error if still haven't finished
        }
        else
            return NGX_OK; // Otherwise confirm end of stream
    }

    for (p = start; p != end; p++)
    {
        switch (upload_ctx->state)
        {
        /*
			 * Seek the boundary
			 */
        case upload_state_boundary_seek:
            if (*p == *upload_ctx->boundary_pos)
                upload_ctx->boundary_pos++;
            else
                upload_ctx->boundary_pos = upload_ctx->boundary_start;

            if (upload_ctx->boundary_pos == upload_ctx->boundary.data + upload_ctx->boundary.len)
            {
                upload_ctx->state = upload_state_after_boundary;
                upload_ctx->boundary_start = upload_ctx->boundary.data;
                upload_ctx->boundary_pos = upload_ctx->boundary_start;
            }
            break;
        case upload_state_after_boundary:
            switch (*p)
            {
            case '\n':
                upload_ctx->state = upload_state_headers;
                upload_ctx->header_accumulator_pos = upload_ctx->header_accumulator;
            case '\r':
                break;
            case '-':
                upload_ctx->state = upload_state_finish;
                break;
            }
            break;
        /*
			 * Collect and store headers
			 */
        case upload_state_headers:
            switch (*p)
            {
            case '\n':
                if (upload_ctx->header_accumulator_pos == upload_ctx->header_accumulator)
                {
                    upload_ctx->is_file = (upload_ctx->file_name.data == 0) || (upload_ctx->file_name.len == 0) ? 0 : 1;

                    rc = upload_start_file(upload_ctx);

                    if (rc != NGX_OK)
                    {
                        upload_ctx->state = upload_state_finish;
                        return rc; // User requested to cancel processing
                    }
                    else
                    {
                        upload_ctx->state = upload_state_data;
                        upload_ctx->output_buffer_pos = upload_ctx->output_buffer;
                    }
                }
                else
                {
                    *upload_ctx->header_accumulator_pos = '\0';

                    rc = upload_parse_part_header(upload_ctx, (char *)upload_ctx->header_accumulator,
                                                  (char *)upload_ctx->header_accumulator_pos);

                    if (rc != NGX_OK)
                    {
                        upload_ctx->state = upload_state_finish;
                        return rc; // Malformed header
                    }
                    else
                        upload_ctx->header_accumulator_pos = upload_ctx->header_accumulator;
                }
            case '\r':
                break;
            default:
                if (upload_ctx->header_accumulator_pos < upload_ctx->header_accumulator_end - 1)
                    *upload_ctx->header_accumulator_pos++ = *p;
                else
                {
                    ngx_log_error(NGX_LOG_ERR, upload_ctx->log, 0, "part header is too long");

                    upload_ctx->state = upload_state_finish;
                    return NGX_ERROR;
                }
                break;
            }
            break;
        /*
			 * Search for separating or terminating boundary
			 * and output data simultaneously
			 */
        case upload_state_data:
            if (*p == *upload_ctx->boundary_pos)
                upload_ctx->boundary_pos++;
            else
            {
                if (upload_ctx->boundary_pos == upload_ctx->boundary_start)
                {
                    // IE 5.0 bug workaround
                    if (*p == '\n')
                    {
                        /*
                             * Set current matched position beyond LF and prevent outputting
                             * CR in case of unsuccessful match by altering boundary_start 
                             */
                        upload_ctx->boundary_pos = upload_ctx->boundary.data + 2;
                        upload_ctx->boundary_start = upload_ctx->boundary.data + 1;
                    }
                    else
                        upload_putc(upload_ctx, *p);
                }
                else
                {
                    // Output partially matched lump of boundary
                    u_char *q;
                    for (q = upload_ctx->boundary_start; q != upload_ctx->boundary_pos; q++)
                        upload_putc(upload_ctx, *q);

                    p--; // Repeat reading last character

                    // And reset matched position
                    upload_ctx->boundary_start = upload_ctx->boundary.data;
                    upload_ctx->boundary_pos = upload_ctx->boundary_start;
                }
            }

            if (upload_ctx->boundary_pos == upload_ctx->boundary.data + upload_ctx->boundary.len)
            {
                upload_ctx->state = upload_state_after_boundary;
                upload_ctx->boundary_pos = upload_ctx->boundary_start;

                upload_flush_output_buffer(upload_ctx);
                if (!upload_ctx->discard_data)
                    upload_finish_file(upload_ctx);
                else
                    upload_abort_file(upload_ctx);
            }
            break;
        /*
			 * Skip trailing garbage
			 */
        case upload_state_finish:
            break;
        }
    }

    return NGX_OK;
}

static ngx_int_t upload_process_raw_buf(jjyun_upload_ctx_t *upload_ctx, u_char *start, u_char *end)
{
    ngx_int_t rc;
    if (start == end)
    {
        if (!upload_ctx->discard_data)
            upload_finish_file(upload_ctx);
        else
            upload_abort_file(upload_ctx);
        return NGX_OK;
    }

    if (!upload_ctx->started)
    {
        rc = upload_start_file(upload_ctx);

        if (rc != NGX_OK)
        {
            return rc;
        }

        upload_ctx->started = 1;
    }

    if (upload_ctx->flush_output_buffer_f)
        if (upload_ctx->flush_output_buffer_f(upload_ctx, (void *)start, (size_t)(end - start)) != NGX_OK)
            upload_ctx->discard_data = 1;

    return NGX_OK;
}

static ngx_int_t ngx_http_process_request_body(ngx_http_request_t *r, ngx_chain_t *body)
{
    ngx_int_t rc;
    jjyun_upload_ctx_t *u = get_jjyun_upload_ctx_t();

    while (body)
    {
        rc = u->data_handler(u, body->buf->pos, body->buf->last);

        if (rc != NGX_OK)
            return rc;

        body = body->next;
    }

    if (u->raw_input)
    {
        // Signal end of body
        if (r->request_body->rest == 0)
        {
            rc = u->data_handler(u, NULL, NULL);

            if (rc != NGX_OK)
                return rc;
        }
    }
    return NGX_OK;
}
//文件处理完成

void jjyun_upload_file_callback(ngx_fdfs_upstream_t *u)
{
    ngx_http_request_t *r = u->r;
    ngx_http_jjyun_ctx_t *jjctx = (ngx_http_jjyun_ctx_t *)u->data;
    ngx_buf_t **b = &jjctx->response;
    if (u->nerr == 0)
    {
        char fileid[144] = {0};
        memcpy(fileid,u->content->start,u->content->end - u->content->start );
        jjyun_format_response(r, b, 0, "%s%c%s%Z", fileid,FDFS_FILE_ID_SEPERATOR,fileid + FDFS_GROUP_NAME_MAX_LEN);
    }
    else
    {
        jjyun_format_response(r, b, u->nerr, "");
    }

    ngx_http_finalize_request(r, jjyun_send_response(jjctx));
}

static ngx_int_t jjyun_upload_body_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_http_jjyun_ctx_t *u = ngx_http_get_module_ctx(r, ngx_http_jjyun_module);
    jjyun_upload_ctx_t *upload_ctx = u->type_ctx;
    ngx_buf_t **b = &u->response;
    if (upload_ctx->prevent_output == 1)
    {
        jjyun_format_response(r, b, JJYUN_ERROR_AGAIN, "");
        ngx_http_finalize_request(r, jjyun_send_response(u));
    }
    else
    {
        ngx_upload_cleanup_t *ucln;
        ucln = upload_ctx->cln->data;
        ucln->clean = 1;

        if ((rc = ngx_jjyun_upload_file(r, &upload_ctx->output_file.name, u, jjyun_upload_file_callback)) != NGX_OK)
        {
            jjyun_format_response(r, b, rc, "");
            ngx_http_finalize_request(r, jjyun_send_response(u));
            return rc;
        }
    }
    return NGX_OK;
}
/**
 * 文件接受相关
*/
static ngx_int_t ngx_http_do_read_upload_client_request_body(ngx_http_request_t *r)
{
    ssize_t size, n, limit;
    ngx_connection_t *c;
    ngx_http_request_body_t *rb;
    jjyun_upload_ctx_t *u = get_jjyun_upload_ctx_t();
    ngx_int_t rc;
    ngx_http_core_loc_conf_t *clcf;
    ngx_msec_t delay;

    c = r->connection;
    rb = r->request_body;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http read client request body");
    for (;;)
    {
        for (;;)
        {
            if (rb->buf->last == rb->buf->end)
            {
                rc = ngx_http_process_request_body(r, u->to_write);
                if (rc != NGX_OK)
                {
                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }

                u->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs;
                rb->buf->last = rb->buf->start;
            }

            size = rb->buf->end - rb->buf->last;

            if ((off_t)size > rb->rest)
            {
                size = (size_t)rb->rest;
            }

            if (u->limit_rate)
            {
                limit = u->limit_rate * (ngx_time() - r->start_sec + 1) - u->received;

                if (limit < 0)
                {
                    c->read->delayed = 1;
                    ngx_add_timer(c->read,
                                  (ngx_msec_t)(-limit * 1000 / u->limit_rate + 1));

                    return NGX_AGAIN;
                }

                if (limit > 0 && size > limit)
                {
                    size = limit;
                }
            }

            n = c->recv(c, rb->buf->last, size);

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http client request body recv %z", n);

            if (n == NGX_AGAIN)
            {
                break;
            }

            if (n == 0)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client closed prematurely connection");
            }

            if (n == 0 || n == NGX_ERROR)
            {
                c->error = 1;
                return NGX_HTTP_BAD_REQUEST;
            }

            rb->buf->last += n;
            rb->rest -= n;
            r->request_length += n;
            u->received += n;

            if (rb->rest == 0)
            {
                break;
            }

            if (rb->buf->last < rb->buf->end)
            {
                break;
            }

            if (u->limit_rate)
            {
                delay = (ngx_msec_t)(n * 1000 / u->limit_rate + 1);

                if (delay > 0)
                {
                    c->read->delayed = 1;
                    ngx_add_timer(c->read, delay);
                    return NGX_AGAIN;
                }
            }
        }

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http client request body rest %uz", rb->rest);

        if (rb->rest == 0)
        {
            break;
        }

        if (!c->read->ready)
        {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
            ngx_add_timer(c->read, clcf->client_body_timeout);

            if (ngx_handle_read_event(c->read, 0) != NGX_OK)
            {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            return NGX_AGAIN;
        }
    }

    if (c->read->timer_set)
    {
        ngx_del_timer(c->read);
    }

    rc = ngx_http_process_request_body(r, u->to_write);

    if (rc != NGX_OK)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    upload_shutdown_ctx(u);
    return jjyun_upload_body_handler(r);
}

static void ngx_http_read_upload_client_request_body_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    jjyun_upload_ctx_t *u = get_jjyun_upload_ctx_t();

    ngx_event_t *rev = r->connection->read;
    ngx_http_core_loc_conf_t *clcf;

    if (rev->timedout)
    {
        if (!rev->delayed)
        {
            r->connection->timedout = 1;
            upload_shutdown_ctx(u);
            ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
            return;
        }

        rev->timedout = 0;
        rev->delayed = 0;

        if (!rev->ready)
        {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, clcf->client_body_timeout);

            if (ngx_handle_read_event(rev, clcf->send_lowat) != NGX_OK)
            {
                upload_shutdown_ctx(u);
                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            }

            return;
        }
    }
    else
    {
        if (r->connection->read->delayed)
        {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                           "http read delayed");

            if (ngx_handle_read_event(rev, clcf->send_lowat) != NGX_OK)
            {
                upload_shutdown_ctx(u);
                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            }

            return;
        }
    }

    rc = ngx_http_do_read_upload_client_request_body(r);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE)
    {
        upload_shutdown_ctx(u);
        ngx_http_finalize_request(r, rc);
    }
}

ngx_int_t jjyun_read_upload_client_request_body(ngx_http_request_t *r)
{
    ssize_t size, preread;
    ngx_buf_t *b;
    ngx_chain_t *cl, **next;
    ngx_http_request_body_t *rb;
    ngx_http_core_loc_conf_t *clcf;
    jjyun_upload_ctx_t *u = get_jjyun_upload_ctx_t();

    r->main->count++;
    if (r->request_body || r->discard_body)
    {
        return NGX_OK;
    }

    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
    if (rb == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->request_body = rb;

    if (r->headers_in.content_length_n <= 0)
    {
        return NGX_HTTP_BAD_REQUEST;
    }

    preread = r->header_in->last - r->header_in->pos;

    if (preread)
    {
        /* there is the pre-read part of the request body */
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http client request body preread %uz", preread);

        u->received = preread;

        b = ngx_calloc_buf(r->pool);
        if (b == NULL)
        {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        b->temporary = 1;
        b->start = r->header_in->pos;
        b->pos = r->header_in->pos;
        b->last = r->header_in->last;
        b->end = r->header_in->end;

        rb->bufs = ngx_alloc_chain_link(r->pool);
        if (rb->bufs == NULL)
        {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        rb->bufs->buf = b;
        rb->bufs->next = NULL;
        rb->buf = b;

        if (preread >= r->headers_in.content_length_n)
        {
            /* the whole request body was pre-read */
            r->header_in->pos += r->headers_in.content_length_n;
            r->request_length += r->headers_in.content_length_n;

            if (ngx_http_process_request_body(r, rb->bufs) != NGX_OK)
            {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            return jjyun_upload_body_handler(r);
        }

        /*
         * to not consider the body as pipelined request in
         * ngx_http_set_keepalive()
         */
        r->header_in->pos = r->header_in->last;
        r->request_length += preread;
        rb->rest = r->headers_in.content_length_n - preread;

        if (rb->rest <= (off_t)(b->end - b->last))
        {
            /* the whole request body may be placed in r->header_in */
            u->to_write = rb->bufs;
            r->read_event_handler = ngx_http_read_upload_client_request_body_handler;
            return ngx_http_do_read_upload_client_request_body(r);
        }

        next = &rb->bufs->next;
    }
    else
    {
        b = NULL;
        rb->rest = r->headers_in.content_length_n;
        next = &rb->bufs;
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    size = clcf->client_body_buffer_size;
    size += size >> 2;

    if (rb->rest < (ssize_t)size)
    {
        size = rb->rest;

        if (r->request_body_in_single_buf)
        {
            size += preread;
        }
    }
    else
    {
        size = clcf->client_body_buffer_size;
        b = NULL;
    }

    rb->buf = ngx_create_temp_buf(r->pool, size);
    if (rb->buf == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    cl->buf = rb->buf;
    cl->next = NULL;

    if (b && r->request_body_in_single_buf)
    {
        size = b->last - b->pos;
        ngx_memcpy(rb->buf->pos, b->pos, size);
        rb->buf->last += size;
        next = &rb->bufs;
    }
    *next = cl;

    u->to_write = rb->bufs;
    r->read_event_handler = ngx_http_read_upload_client_request_body_handler;
    return ngx_http_do_read_upload_client_request_body(r);
}

/**
 * 文件上传处理函数
*/
static ngx_int_t jjyun_upload_handler(ngx_http_jjyun_ctx_t *jjctx)
{
    ngx_int_t rc;
    ngx_http_request_t *r = jjctx->r;
    if (NULL == jjctx->type_ctx)
    {
        jjctx->type_ctx = ngx_pcalloc(r->pool, sizeof(jjyun_upload_ctx_t));
        if (jjctx->type_ctx == NULL)
        {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }
    jjyun_upload_ctx_t *u = (jjyun_upload_ctx_t *)jjctx->type_ctx;
    upload_init_ctx(u, r);

    if (upload_parse_request_headers(u, &r->headers_in) != NGX_OK)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;

    if (upload_start(u) != NGX_OK)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;

    rc = jjyun_read_upload_client_request_body(r);
    if (rc >= NGX_HTTP_SPECIAL_RESPONSE)
    {
        return rc;
    }
    return NGX_DONE;
}